#region Copyright Notice
// 
// 
// Copyright (c) 2009-2010 Anindya Chatterjee
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// 
// 
// 
#endregion
using System;
using System.Collections.Specialized;
using System.Net;
using System.Serialization;
using GCore.Authentication;

namespace GCore
{
    public partial class GService<TFeed, TEntry>
        where TFeed : GFeed<TEntry>, new()
        where TEntry : GEntry, new()
    {
        private readonly HybridDictionary _clientStore = new HybridDictionary();
        private WebClient _client;

        #region GetAllFeed Async

        /// <summary>
        /// Occurs when an asynchronous request for <see cref="GetAllFeedAsync"/> operation completes.
        /// </summary>
        protected event GetAllFeedCompletedEventHandler<TFeed, TEntry> GetAllFeedCompleted;

        /// <summary>
        /// Gets all feeds from <see cref="GUrl"/> specified for a particular Google service. This method doesnot block the calling thread. 
        /// </summary>
        /// <param name="userState">A user-defined object that is passed to the method invoked when the asynchronous operation completes.</param>
        protected virtual void GetAllFeedAsync(object userState)
        {
            if (String.IsNullOrEmpty(GUrl))
                throw new ArgumentNullException(GUrl,
                                                "Feed Url is not provided. Please provide Feed url first through GUrl Property.");
            var client = new WebClient();
            if (ProxySettings != null)
                client.Proxy = ProxySettings;
            client.Credentials = new NetworkCredential
                                     {
                                         UserName = GUserName,
                                         Password = GPassword
                                     };

            if (userState != null)
                lock (_clientStore.SyncRoot)
                {
                    _clientStore.Add(userState, client);
                }
            else
                _client = client;

            client.DownloadStringCompleted += ClientDownloadStringCompleted;
            client.DownloadStringAsync(new Uri(GUrl));
        }

        /// <summary>
        /// Cancels a pending asynchronous operation for <see cref="GetAllFeedAsync"/>.
        /// </summary>
        /// <param name="userState">A user-defined object to uniquely determine the asynchronous operation.</param>
        protected virtual void GetAllFeedAsyncCancel(object userState)
        {
            lock (_clientStore.SyncRoot)
            {
                if (userState == null || !_clientStore.Contains(userState))
                {
                    _client.CancelAsync();
                    return;
                }
                var client = _clientStore[userState] as WebClient;
                if (client != null) client.CancelAsync();
            }
        }

        private void ClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (GetAllFeedCompleted == null || e.Cancelled) return;
            var args = new GetAllFeedCompletedEventArgs<TFeed, TEntry>(e.Result, e.Error, e.Cancelled, e.UserState);
            GetAllFeedCompleted(sender, args);
        }

        #endregion

        #region SubmitNewEntry Async

        /// <summary>
        /// Occurs when an asynchronous request for <see cref="SubmitNewEntryAsync"/> operation completes.
        /// </summary>
        protected event SubmitNewEntryCompletedEventHandler<TFeed, TEntry> SubmitNewEntryCompleted;

        /// <summary>
        /// Submits a new atom entry to a particular Google service. This method doesnot block the calling thread.
        /// </summary>
        /// <param name="entry">The atom entry object containing the data to submit.</param>
        /// <param name="applicationName">The name of the application which invokes this method.</param>
        /// <param name="userState">A user-defined object that is passed to the method invoked when the asynchronous operation completes.</param>
        protected virtual void SubmitNewEntryAsync(TEntry entry, string applicationName, object userState)
        {
            if (String.IsNullOrEmpty(GUrl))
                throw new ArgumentNullException(GUrl,
                                                "Feed Url is not provided. Please provide Feed url first through GUrl Property.");
            var client = new WebClient();
            if (ProxySettings != null)
                client.Proxy = ProxySettings;

            var serializer = new XObjectSerializer<TEntry>();
            string xml = serializer.StringSerialize(entry);

            var gc = new GDataCredentials(GUserName, GPassword)
                         {
                             AccountType = "GOOGLE"
                         };
            string header = GAuthManager.RequestClientLoginHeader(gc, "code", applicationName, true,
                                                                  GoogleAuthentication.URI_HANDLER, ProxySettings);
            client.Credentials = new NetworkCredential
                                     {
                                         UserName = GUserName,
                                         Password = GPassword
                                     };

            client.Headers.Add(header);
            client.Headers.Set(HttpRequestHeader.ContentType, "application/atom+xml");

            if (userState != null)
                lock (_clientStore.SyncRoot)
                {
                    _clientStore.Add(userState, client);
                }
            else
                _client = client;

            client.UploadStringCompleted += ClientUploadStringCompleted;
            client.UploadStringAsync(new Uri(GUrl), "POST", xml, userState);
        }

        /// <summary>
        /// Cancels a pending asynchronous operation for <see cref="SubmitNewEntryAsync"/>.
        /// </summary>
        /// <param name="userState">A user-defined object to uniquely determine the asynchronous operation.</param>
        protected void SubmitNewEntryAsyncCancel(object userState)
        {
            lock (_clientStore.SyncRoot)
            {
                if (userState == null || !_clientStore.Contains(userState))
                {
                    _client.CancelAsync();
                    return;
                }
                var client = _clientStore[userState] as WebClient;
                if (client != null) client.CancelAsync();
            }
        }

        private void ClientUploadStringCompleted(object sender, UploadStringCompletedEventArgs e)
        {
            if (SubmitNewEntryCompleted == null || e.Cancelled) return;
            var args = new SubmitNewEntryCompletedEventArgs<TFeed, TEntry>(e.Result, e.Error, e.Cancelled,
                                                                           e.UserState);
            SubmitNewEntryCompleted(sender, args);
        }

        #endregion

        #region Query Async

        /// <summary>
        ///  Occurs when an asynchronous request for <see cref="QueryAsync"/> operation completes.
        /// </summary>
        protected event GetAllFeedCompletedEventHandler<TFeed, TEntry> QueryCompleted;

        /// <summary>
        /// Queries a particular Google service using query parameters. This method doesnot block the calling thread.
        /// </summary>
        /// <param name="query"><see cref="IGQuery"/> object, filled with data.</param>
        /// <param name="userState">A user-defined object that is passed to the method invoked when the asynchronous operation completes.</param>
        protected virtual void QueryAsync(IGQuery query, object userState)
        {
            if (String.IsNullOrEmpty(GUrl))
                throw new ArgumentNullException(GUrl,
                                                "Feed Url is not provided. Please provide Feed url first through GUrl Property.");

            string queryUrl = GUrl + "?" + query.GetQueryUrl();
            var queryUri = new Uri(queryUrl);

            var client = new WebClient();
            if (ProxySettings != null)
                client.Proxy = ProxySettings;
            client.Credentials = new NetworkCredential
                                     {
                                         UserName = GUserName,
                                         Password = GPassword
                                     };

            if (userState != null)
                lock (_clientStore.SyncRoot)
                {
                    _clientStore.Add(userState, client);
                }
            else
                _client = client;

            client.DownloadStringCompleted += QueryClientDownloadStringCompleted;
            client.DownloadStringAsync(queryUri);
        }

        /// <summary>
        /// Cancels a pending asynchronous operation for <see cref="QueryAsync"/>.
        /// </summary>
        /// <param name="userState">A user-defined object to uniquely determine the asynchronous operation.</param>
        protected virtual void QueryAsyncCancel(object userState)
        {
            lock (_clientStore.SyncRoot)
            {
                if (userState == null || !_clientStore.Contains(userState))
                {
                    _client.CancelAsync();
                    return;
                }
                var client = _clientStore[userState] as WebClient;
                if (client != null) client.CancelAsync();
            }
        }

        private void QueryClientDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (QueryCompleted == null || e.Cancelled) return;
            var args = new GetAllFeedCompletedEventArgs<TFeed, TEntry>(e.Result, e.Error, e.Cancelled, e.UserState);
            QueryCompleted(sender, args);
        }

        #endregion
    }
}